package apibasej.db.transaction;

import java.sql.Blob;
import java.util.ArrayList;
import java.util.Collection;

import apibasej.db.metadata.Col;
import apibasej.db.sql.wrapper.ColSelect;

/**
 * http://code.google.com/p/apibasej/
 * 
 * @author Julio Azevedo Lessa - apibasej@gmail.com
 * 
 */
public class Row {
	
	private ArrayList<ColSelect<?>> wrapperCols = new ArrayList<ColSelect<?>>();
	//XXX private ArrayList<String> aliases = new ArrayList<String>();
	private ArrayList<Object> values = new ArrayList<Object>();
	
	public Object get(int index){
		return values.get(index); 
	}
	
	public int getColumnCount(){
		return values.size();
	}
	
	//***************************************************************************************************
	
	interface AuxSearch<K,I>{ // K: tipo do objeto para buscar, I: typo do item da lista
		boolean isEqual(K key, I itemList);
	}
	private <K,I> Integer searchIndex(AuxSearch<K,I> aux, K key, Collection<I> listToFind) throws UtilDBException{
		ArrayList<Integer> foundIndex = new ArrayList<Integer>();// usa lista para validar se achou mais de um
		int ct = 0;
		for(I item : listToFind){
			if(aux.isEqual(key, item)) foundIndex.add(ct);
			ct++;
		}
		if(foundIndex.size()==0) return null;// não achou
		else if(foundIndex.size()>1) throw new UtilDBException("More than one column was found for the key: "+key);
		else return foundIndex.get(0);//ok
	}
	
	//***************************************************************************************************
	
	public Object get(String alias) throws UtilDBException{
		return values.get( getIndex(alias) );
	}
	public int getIndex(String alias) throws UtilDBException{
		//-----------------------------------------
		Integer index;
		/* XXX
		//### primeiro com alias igual
		AuxSearch<String,String> byAlias = new AuxSearch<String,String>() {
			@Override public boolean isEqual(String alias, String itemList) {return alias.equalsIgnoreCase(itemList);}
		};
		index = searchIndex(byAlias, alias, aliases);
		if(index!=null) return index;
		*/
		//-----------------------------------------
		//### depois pelos aliases das wrappers cols
		AuxSearch<String,ColSelect<?>> byAliasWr = new AuxSearch<String, ColSelect<?>>() {
			@Override public boolean isEqual(String alias, ColSelect<?> w) {return alias.equalsIgnoreCase(w.getAliasSelect());}
		};
		index = searchIndex(byAliasWr, alias, wrapperCols);
		if(index!=null) return index;
		//-----------------------------------------
		//### depois pela strForColSelect
		AuxSearch<String,ColSelect<?>> byStrColSel = new AuxSearch<String, ColSelect<?>>() {
			@Override public boolean isEqual(String alias, ColSelect<?> w) {return alias.equalsIgnoreCase(w.getStrForColSelect());}
		};
		index = searchIndex(byStrColSel, alias, wrapperCols);
		if(index!=null) return index;
		//-----------------------------------------
		//### depois pelo nome das colunas
		AuxSearch<String,ColSelect<?>> byNameCols = new AuxSearch<String, ColSelect<?>>() {
			@Override public boolean isEqual(String alias, ColSelect<?> w) {return w.getCol()!=null && alias.equalsIgnoreCase(w.getCol().getName());}
		};
		index = searchIndex(byNameCols, alias, wrapperCols);
		if(index!=null) return index;
		//-----------------------------------------
		//### se não achou
		throw new UtilDBException("Out column not found by alias: "+alias); 
	}
	
	//***************************************************************************************************
		
	// este método só deve ser usado se for um select em que colunas de resposta não se repetem
	@SuppressWarnings("unchecked")
	public <T> T get(Col<T> col) throws UtilDBException{
		return (T)values.get(getIndex(col));
	}
	public int getIndex(Col<?> col) throws UtilDBException{
		//-----------------------------------------
		//### primeiro pela coluna
		AuxSearch<Col<?>,ColSelect<?>> byCol = new AuxSearch<Col<?>, ColSelect<?>>() {
			@Override public boolean isEqual(Col<?> col, ColSelect<?> w) {return col.equals(w.getCol());}
		};
		Integer index = searchIndex(byCol, col, wrapperCols);
		if(index!=null) return index;
		//-----------------------------------------
		//### depois pelo nome das colunas
		AuxSearch<Col<?>,ColSelect<?>> byNameCol = new AuxSearch<Col<?>, ColSelect<?>>() {
			@Override public boolean isEqual(Col<?> col, ColSelect<?> w) {return col.getName().equalsIgnoreCase(w.getCol().getName());}
		};
		index = searchIndex(byNameCol, col, wrapperCols);
		if(index!=null) return index;
		//-----------------------------------------
		//### depois pelos aliases
		/* XXX
		AuxSearch<Col<?>,String> byAlias = new AuxSearch<Col<?>, String>() {
			@Override public boolean isEqual(Col<?> col, String a) {return (col.getName().equalsIgnoreCase(a) || col.getNameWithTab().equalsIgnoreCase(a));}
		};
		index = searchIndex(byAlias, col, aliases);
		if(index!=null) return index;
		*/
		//-----------------------------------------
		//### se não achou
		throw new UtilDBException("Out column not found by col: "+col.getNameWithTab()); 
	}
	
	//***************************************************************************************************
	
	@SuppressWarnings("unchecked")
	public <T> T get(Object identCol) throws UtilDBException{
		if(identCol instanceof Col<?>){
			Col<?> c = (Col<?>)identCol;
			return (T)get(c);
		}else if(identCol instanceof ColSelect<?>){
			ColSelect<?> c = (ColSelect<?>)identCol;
			return (T)get(c);
		}else if(identCol instanceof Integer){
			Integer c = (Integer)identCol;
			return (T)get(c);			
		}else if(identCol instanceof String){
			String c = (String)identCol;
			return (T)get(c);
		}else{
			throw new UtilDBException("Invalid type for column identifier: "+identCol);
		}
	}
	
	//***************************************************************************************************
	
	@SuppressWarnings("unchecked")
	public <T> T get(ColSelect<T> wrapperCol) throws UtilDBException{
		return (T)values.get(getIndex(wrapperCol));
	}
	public int getIndex(ColSelect<?> wrapperCol) throws UtilDBException{
		//-----------------------------------------
		//### primeiro se achar ColSelect igual
		AuxSearch<ColSelect<?>,ColSelect<?>> byColSel = new AuxSearch<ColSelect<?>, ColSelect<?>>() {
			@Override public boolean isEqual(ColSelect<?> wrapperCol, ColSelect<?> w) {return wrapperCol==w;}
		};
		Integer index = searchIndex(byColSel, wrapperCol, wrapperCols);
		if(index!=null) {
			return index;
		}
		//-----------------------------------------
		/* XXX
		AuxSearch<ColSelect<?>,String> SSS = new AuxSearch<ColSelect<?>, String>() {
			@Override public boolean isEqual(ColSelect<?> wrapperCol, String a) {return wrapperCol.getAliasSelect()!=null && wrapperCol.getAliasSelect().equalsIgnoreCase(a);}
		};
		index = searchIndex(SSS, wrapperCol, aliases);
		if(index!=null) {
			return index;
		}*/
		//-----------------------------------------
		//### se não achou
		throw new UtilDBException("Out column not found by ColSelect: "+wrapperCol.getStrForColSelect()); 
	}
	
	//***************************************************************************************************
	
	
	@SuppressWarnings("unchecked")
	public byte[] getBytes(Col<Blob> col, GenericTransaction c, boolean closeAfterRead) throws UtilDBException{
		return getBytes((ColSelect<Blob>)wrapperCols.get(getIndex(col)), c, closeAfterRead);
	}
	public byte[] getBytes(ColSelect<Blob> wrapperCol, GenericTransaction c, boolean closeAfterRead) throws UtilDBException{
		if(c!=null && c.isAutomaticClose()) throw new UtilDBException("Transaction must be automaticClose==false for read BLOBs");
		Blob b = get(wrapperCol);
		try {
			long len = b.length();
			if(len>Integer.MAX_VALUE) throw new UtilDBException("Blob size greater than Integer.MAX_VALUE. blob.length()="+len);
			return b.getBytes(1,(int)b.length()); // se tamanho do blob for long irá cortar um pedaço, mas o tamanho máximo do array de bytes é int ...
			//return new UtilIO().readInputStream(b.getBinaryStream());
		} catch (Exception e) {
			throw new UtilDBException("Error on read BLOB: "+e.getMessage(),e);
		}finally{
			if(closeAfterRead && c!=null) c.close();
		}
	}
	
	//***************************************************************************************************
	
	public int numCols(){
		return values.size();
	}
	
	public void add(ColSelect<?> colSelect, Object value) throws UtilDBException{
		wrapperCols.add(colSelect);
		//XXX aliases.add(colSelect.getAliasSelect()); // tem que adicionar mesmo se tiver null, para manter a ordem das colunas
		values.add(value);
	}
	
	/* XXX
	public void add(String alias, Object value) throws UtilDBException{// usado se setar o sql e quiser pegar o retorno pelo alias da consulta ...
		// não deve validar aqui, somente na hora de recuperar, pois o select * com várias tabelas pode ter várias colunas repetidas ...
		//XXX aliases.add(alias);
		values.add(value);
	}*/
	
	public ArrayList<ColSelect<?>> getWrapperCols() {
		return wrapperCols;
	}
	
	/* XXX
	public ArrayList<String> getAliases() {
		return aliases;
	}*/
	
	public ArrayList<Object> getValues() {
		return values;
	}
	
	@Override
	public boolean equals(Object obj) {
		return this==obj;
	}
	
}
